
static char RCSId[]="$Id: FunctionObject.m,v 1.1.1.1 1993/03/18 03:33:14 davis Exp $";


#import "FunctionObject.h"

#import <sys/stat.h>		/* stat()			*/
#import <sys/types.h>		/* stat()			*/

#import <objc/hashtable.h>	/* NXCopyStringBufferFromZone()	*/
#import <objc/zone.h>

#import <ctype.h>		/* isspace()			*/
#import <libc.h>		/* index(), rindex()		*/


const char *styleString[] = {"lines", "points", "linespoints", "dots",
			     "impulses", "errorbars", "boxes", "boxerrorbars"};


/*  Is the string blank (i.e. full of only white space)?  */
static BOOL _isBlank (const char *aString)
{
    const char *cur;

    for (cur = aString ; *cur != '\0' ; cur++)
	if (*cur != ' ' && *cur != '\t' && *cur != '\n')
	    return NO;

    return YES;
}


static BOOL _isSpaceNotNL (char aChar)
{
    return (isspace (aChar)) && (aChar != '\n');
}




/*  
 *  aString is assumed to be a one-line string (i.e it contains no 
 *  newlines).  This could be changed easily if need be.
 */
static void _removeComments (char *aString)
{
    char *cur = aString;
    char quote[100];
    int number = 0;

    while (*cur) {
	if ((*cur == '\'') || (*cur == '"')) {

	    /*  
	     *  The quote is either closing a previous quotation or 
	     *  starting a new one.  We look at the last quote 
	     *  encountered to see which.
	     */
	    if (number && (*cur == quote[number - 1]))	/* closing previous */
		number--;
	    else					/* starting new	    */
		quote[number++] = *cur;

	} else if (number && (*cur == '#')) {

	    /*  
	     *  If we've encountered a pound sign outside of a 
	     *  quotation, we've found a comment.  The rest of the 
	     *  line can be truncated.
	     */
	    cur = '\0';

	}

	cur++;
    }

}


static void _getWith (char *aString, int *style, int *lineStyle,
		      int *pointsStyle)
{
    char *cur, *with;

    /* 
     *  This attempts to detect a 'with' phrase and to extract the 
     *  style from it if it exists.  There are several ways to trick 
     *  this. 
     */
    if ((with = rindex (aString, 'w')) && (*(with+1) == 'i') &&
	(cur = index (with, ' '))) {
	switch (*(++cur)) {
	case 'p':  *style = FUNCTION_POINTS;	break;
	case 'd':  *style = FUNCTION_DOTS;	break;
	case 'i':  *style = FUNCTION_IMPULSES;	break;
	case 'e':  *style = FUNCTION_ERRORBARS;	break;
	case 'b':
	    if (index (cur, 'r')) *style = FUNCTION_BOXERRORBARS;
	    else *style = FUNCTION_BOXES;
	    break;
	case 'l':
	    if (index (cur, 'p')) *style = FUNCTION_LINESPOINTS;
	    else *style = FUNCTION_LINES;
	    break;
	default:   *style = FUNCTION_NOSTYLE;	break;
	}
	*with = '\0';

	/* 
	 *  If the style type includes lines or points, the user has 
	 *  the option of setting the point/line styles.  Check for 
	 *  those...
	 */
						/* Line Style */
	if ((*style == FUNCTION_POINTS) || (*style == FUNCTION_LINES) ||
	    (*style == FUNCTION_LINESPOINTS)) {

	    while (cur && *cur && ((*cur > '6') || (*cur < '1')))
		cur++;
	    if (cur && *cur)
		*lineStyle = *cur - '0';

						/* Point Style */
	    if ((*style == FUNCTION_POINTS) ||
		(*style == FUNCTION_LINESPOINTS))  {

		while (cur && *cur && ((*cur > '6') || (*cur < '1')))
		    cur++;
		if (cur && *cur)
		    *pointsStyle = *cur - '0';
	    }

	}

    }

}




static char *_getTitle (char *aString)
{
    while (aString && *aString) {

	/*
	 *  If this appears to be a "title" phrase, see if a quoted
	 *  string follows.  If one does, chop it off and return it.
	 */
	
	if ((*aString == 't') &&
	    ((*(aString + 1) == 'i') || _isSpaceNotNL (*(aString + 1)))) {

	    char *firstQuote, *beginning = aString;

	    while (!_isSpaceNotNL (*aString))
		aString++;
	    while (_isSpaceNotNL (*aString))
		aString++;
	    if ((*aString == '\'') || (*aString == '"')) {

		/*  
		 *  Okay, we are as sure as can be that this is a 
		 *  "title" phrase.
		 */
		firstQuote = aString;
		if (aString = index (++aString, *firstQuote))
		    *aString = '\0';
		*beginning = '\0';

		return ++firstQuote;
	    }

	}

	aString++;
    }

    return NULL;
}


@implementation FunctionObject


/*  Does the string contain something besides white space?  */
+ (BOOL) isAcceptableStringValue:(const char *)aString
{
    return (aString && !_isBlank (aString));
    /* todo, also check for valid functions, if possible */
}
	

/*  
 *  Makes sure the file whose full path is specified by aString is a 
 *  regular, readable file.
 */
+ (BOOL) isAcceptableDataFile:(const char *)aString
{
    struct stat fileinfo;
    BOOL returnVal;

    returnVal = (aString && !stat (aString, &fileinfo) &&
		 ((fileinfo.st_mode & S_IFMT) == S_IFREG) &&
		 (fileinfo.st_mode & S_IREAD));
    return returnVal;
}



/*  
 *  The attributes of a function are stored by an instance of 
 *  FunctionObject.  The only complicated part is parsing those 
 *  attributes from a Gnuplot "plot" statement.  aString should 
 *  specify only the part of that statement pertaining to the function 
 *  that will be stored by this instance and should contain no leading 
 *  white space.  (This method could be nicer by ignoring leading 
 *  white space.)
 */
- initFromString:(const char *)aString
{
    char quote, *cur;
    NXZone *zone;

    [super init];

    zone = [self zone];

    title = NULL;			/* Defaults */
    style = FUNCTION_NOSTYLE;
    pointsStyle = POINTS_NOSTYLE;
    lineStyle = LINE_NOSTYLE;
    isDataFile = NO;

    if (aString)  {

	stringValue = NXCopyStringBufferFromZone (aString, zone);
	_removeComments (stringValue);
	_getWith (stringValue, &style, &lineStyle, &pointsStyle);
	[self setTitle:_getTitle (stringValue)];

	/* 
	 *  If this string value is quoted, it is a data file.  We 
	 *  remove the quotes and set the isDataFile flag.  The string 
	 *  is assumed to have no leading white space, i.e. if it is a 
	 *  data file, the first character is a quote.
	 */

	quote = *stringValue;
	if ((quote == '\'') || (quote == '"')) {   /* This is a data file */

	    isDataFile = YES;
	    *rindex (stringValue, quote) = '\0';
	    cur = NXCopyStringBufferFromZone (stringValue+1, zone);
	    NXZoneFree (zone, stringValue);
	    stringValue = cur;

	} else {		/* This is a function, not a data file. */

	    /* Remove trailing blanks */
	    cur = stringValue + strlen (stringValue) - 1 ;
	    while (isspace (*cur) && (cur >= stringValue))
		*(cur--) = '\0';

	}

    } else
	stringValue = NULL;
      
  return self;
}
 
 
- free
{
    NXZoneFree ([self zone], title);

    return [super free];
}


- setDataFile:(BOOL)cond
{
    isDataFile = cond;
    return self;
}


- (BOOL)isDataFile
{
    return isDataFile;
}


- setTitle:(const char *)aString
{
    char *aux;
    NXZone *zone = [self zone];

    NXZoneFree (zone, title);
    if (aString) {

	title = NXCopyStringBufferFromZone (aString, zone);

	/* 
	 *  Make sure aString doesn't mix both kinds of quotation marks.  
	 *  Gnuplot can handle one or the other but not both in one title. 
	 *  (If there's a mix, use double quotation marks.)
	 */

	if ( (index (title, '"')) && (aux = index (title, '\'')) ) {
	    do
		*aux = '"';
	    while (aux = index (title, '\''));
	}

    } else
	title = NULL;

    return self;
}


- (const char *)title
{
    return title;
}


- setStyle:(int) anInt
{
    style = anInt;
    return self;
}


- (int)style
{
    return style;
}


- (const char *)styleString
{
    if (style == FUNCTION_NOSTYLE)
	return NULL;
    else
	return styleString[style];
}


- setPointsStyle: (int)anInt
{
    pointsStyle = anInt;
    return self;
}


- (int)pointsStyle
{
    return pointsStyle;
}


- setLineStyle: (int)anInt
{
    lineStyle = anInt;
    return self;
}


- (int)lineStyle
{
    return lineStyle;
}



/*  
 *  If the function is actually a data file, it is similar to an 
 *  attachment, or a "file link," so we return YES.  If the function 
 *  is not a data file, we return NO.
 */
- (BOOL)isAttachment
{
    return isDataFile;
}


// Shuts up the compiler about unused RCSId
- (const char *) rcsid
{
    return RCSId;
}


@end
